use std::collections::{HashMap, HashSet};
use halo_model::ExtensionOperator;
use crate::config::app_error::AppResult;
use crate::extension::index::extension::Comparators::CompareCreationTimestamp;
use crate::extension::index::extension::Comparators;
use crate::extension::model::role;
use crate::extension::model::role::Role;
use crate::store::extension_store_client;

pub const SUPER_ROLE_NAME: &str = "super-role";
pub const ROLE_PREFIX: &str = "ROLE_";

pub async fn list(role_names: &HashSet<String>) -> AppResult<Vec<Role>> {
    let mut roles = Vec::new();
    for role_name in role_names {
        let role = extension_store_client::fetch(role::Role::KIND, &role_name.clone()).await?;
        roles.push(role);
    }
    return Ok(roles);
}

pub async fn list_permissions(names: &HashSet<String>) -> AppResult<Vec<Role>> {
    if contains_super_role(names) {
        let vec = extension_store_client::list_filter(role::Role::KIND, should_filter_hidden(true)).await?;
        return Ok(vec);
    }
    let vec1 = list_dependencies(names, should_filter_hidden(true)).await?;
    return Ok(vec1);
}

fn contains_super_role(names: &HashSet<String>) -> bool {
    names.contains(SUPER_ROLE_NAME)
}

fn should_filter_hidden(filter_hidden: bool) -> Box<dyn Fn(&Role) -> bool + Send + Sync> {
    if !filter_hidden {
        return Box::new(|_| true);
    }
    return Box::new(move |role| {
        let label = role.get_metadata().get_labels().clone();
        if label.is_none() {
            return true;
        }
        let binding = label.unwrap();
        let hidden_value = binding.get(role::HIDDEN_LABEL_NAME);
        return match hidden_value {
            None => false,
            Some(value) => value == "true",
        };
    });
}

pub async fn list_dependencies<F>(
    names: &HashSet<String>,
    additional_predicate: F,
) -> AppResult<Vec<Role>>
where
    F: Fn(&Role) -> bool + Send + 'static + Sync,
{
    let roles = list_roles(names, &additional_predicate).await?;
    let mut visited = HashSet::new();
    let mut all_roles = Vec::new();

    for role in roles {
        let name = role.get_metadata().get_name();
        if !visited.contains(&name) {
            visited.insert(name);

            let mut annotations = role.get_metadata().get_annotations().unwrap_or(HashMap::new());

            let dependencies_json = annotations.remove(role::HIDDEN_LABEL_NAME);
            let dependencies: Vec<String> = match dependencies_json {
                Some(s) => serde_json::from_str(&s).unwrap_or_else(|_| Vec::new()),
                None => Vec::new(),
            };

            let deps = dependencies
                .iter()
                .filter(|&t| !visited.contains(t))
                .cloned()
                .collect::<HashSet<String>>();
            let list_roles = list_roles(&deps, &additional_predicate).await?;
            all_roles.extend(list_roles);
        }
    }
    let vec = list_aggregate_role(&visited, &additional_predicate).await?;
    all_roles.extend(vec);

    Ok(all_roles)
}

pub async fn list_roles<F>(names: &HashSet<String>, additional_predicate: F) -> AppResult<Vec<Role>>
where
    F: Fn(&Role) -> bool + Send + Sync,
{
    let predicate = |role: &Role| names.contains(&role.get_metadata().get_name());

    let predicate = |role: &Role| predicate(role) && additional_predicate(role);

    let mut vec = extension_store_client::list_filter(role::Role::KIND, predicate).await?;
    vec.sort_by(Comparators::comparing(CompareCreationTimestamp(true)));
    return Ok(vec);
}

pub async fn list_aggregate_role<F>(
    role_names: &HashSet<String>,
    additional_predicate: F,
) -> AppResult<Vec<Role>>
where
    F: Fn(&Role) -> bool + Send + Sync,
{
    let aggregated_label_names: Vec<String> = role_names
        .iter()
        .map(|role_name| format!("{}{}", role::ROLE_AGGREGATE_LABEL_PREFIX, role_name))
        .collect();
    let predicate = |role: &Role| {
        let labels = role.get_metadata().get_labels();

        if labels.is_none() {
            return false;
        }
        let labels = labels.unwrap();

        aggregated_label_names.iter().any(|aggregated_label| {
            labels.get(aggregated_label).unwrap_or(&String::from("")) == "true"
        })
    };

    let predicate = |role: &Role| predicate(role) && additional_predicate(role);
    return extension_store_client::list_filter_comparator(
        role::Role::KIND,
        predicate,
        Comparators::CompareCreationTimestamp(true),
    )
    .await;
}
